/*
 * ModelCC, distributed under ModelCC Shared Software License, www.modelcc.org
 */

package org.modelcc.parser.fence;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.modelcc.language.syntax.Grammar;
import org.modelcc.language.syntax.InputSymbol;
import org.modelcc.lexer.LexicalGraph;

/**
 * Parse Graph.
 * A concrete syntax tree or parse tree or parsing tree or derivation tree is 
 * an ordered, rooted tree that represents the syntactic structure of a string 
 * according to some context-free grammar. 
 * 
 * @author Luis Quesada (lquesada@modelcc.org), refactored by Fernando Berzal (berzal@modelcc.org)
 */
public final class ParsedGraph implements Serializable 
{
	/**
	 * Lexical graph
	 */
	private LexicalGraph lg;

    /**
     * Grammar.
     */
    private Grammar grammar;

    /**
     * Set of symbols used in this graph.
     */
    private Set<ParsedSymbol> symbols;

    /**
     * Set of start symbols of this graph.
     */
    private Set<ParsedSymbol> start;

    /**
     * List of preceding symbols.
     */
    private Map<ParsedSymbol,Set<ParsedSymbol>> preceding;

    /**
     * List of following symbols.
     */
    private Map<ParsedSymbol,Set<ParsedSymbol>> following;

    /**
     * Start positions
     */
    private Map<Integer,Set<ParsedSymbol>> startPositions;

    /**
     * End positions
     */
    private Map<Integer,Set<ParsedSymbol>> endPositions;

    /**
     * Constructor.
     * @param grammar Language grammar
     * @param lg Lexical graph
     */
    public ParsedGraph (Grammar grammar, LexicalGraph lg) 
    {        
        this.lg = lg;
        this.grammar = grammar;

        this.symbols = new HashSet<ParsedSymbol>();
        this.start = new HashSet<ParsedSymbol>();;
        this.preceding = new HashMap<ParsedSymbol,Set<ParsedSymbol>>();
        this.following = new HashMap<ParsedSymbol,Set<ParsedSymbol>>();
        this.startPositions = new HashMap<Integer,Set<ParsedSymbol>>();
        this.endPositions = new HashMap<Integer,Set<ParsedSymbol>>();
    }


    // Language grammar

    public Grammar getGrammar() 
    {
        return grammar;
    }

    // Lexical graph
    
    public LexicalGraph getLexicalGraph ()
    {
    	return lg;
    }

    // Symbols in the graph

    public Set<ParsedSymbol> getSymbols() 
    {
        return Collections.unmodifiableSet(symbols);
    }

    public void addSymbol (ParsedSymbol s)
    {
    	symbols.add(s);
    	
    	// Start/end position
    	
        Set<ParsedSymbol> aux;
        int i;
    	
        i = s.getStartIndex();
        aux = startPositions.get(i);
        if (aux == null) {
            aux = new HashSet<ParsedSymbol>();
            startPositions.put(i,aux);
        }
        aux.add(s);
        
        i = s.getEndIndex();
        aux = endPositions.get(i);
        if (aux == null) {
            aux = new HashSet<ParsedSymbol>();
            endPositions.put(i,aux);
        }
        aux.add(s);
    }
    
    public boolean contains (InputSymbol s)
    {
    	return symbols.contains(s);
    }
    
    // Start symbols

    public Set<ParsedSymbol> getStart() 
    {
        return Collections.unmodifiableSet(start);
    }
    
    public void addStart (ParsedSymbol s)
    {
    	start.add(s);
    }

    // Preceding/following relationships

    protected Map<ParsedSymbol, Set<ParsedSymbol>> getPreceding() 
    {
        return Collections.unmodifiableMap(preceding);
    }
    
    public Set<ParsedSymbol> getPreceding (InputSymbol s)
    {
    	return preceding.get(s);
    }

    protected Map<ParsedSymbol, Set<ParsedSymbol>> getFollowing() 
    {
        return Collections.unmodifiableMap(following);
    }
    
    public Set<ParsedSymbol> getFollowing (InputSymbol s)
    {
    	return following.get(s);
    }

    // Start/end positions
    
    public Set<ParsedSymbol> getSymbolsStartingAt(int position)
    {
    	return startPositions.get(position);
    }
    
    public Set<ParsedSymbol> getSymbolsEndingAt (int position)
    {
    	return endPositions.get(position);
    }

    // Preceding/following symbols
    
    /**
     * Add a parsed symbol to the set of another parsed symbol.
     * @param t1 the parsed symbol.
     * @param t2 the parsed symbol to be added.
     */
    private void addMapElement(Map<ParsedSymbol,Set<ParsedSymbol>> target,ParsedSymbol t1,ParsedSymbol t2) 
    {
        Set<ParsedSymbol> set = target.get(t1);
        if (set == null) {
            set = new HashSet<ParsedSymbol>();
            target.put(t1,set);
        }
        set.add(t2);
    }

    /**
     * Remove a parsed symbol from the set of another parsed symbol.
     * @param t1 the parsed symbol.
     * @param t2 the parsed symbol to be removed.
     */
    private void removeMapElement(Map<ParsedSymbol,Set<ParsedSymbol>> target,InputSymbol t1,InputSymbol t2) 
    {
        Set<ParsedSymbol> set = target.get(t1);
        if (set == null) {
            return;
        }
        set.remove(t2);
        if (set.isEmpty())
            target.remove(t1);
    }
    
    /**
     * Adds a preceding/following relationship between parsed symbols t1 and t2.
     * @param t1 the preceding parsed symbol.
     * @param t2 the following parsed symbol.
     */
    public void addLink (ParsedSymbol t1,ParsedSymbol t2) 
    {
        addMapElement(preceding,t2,t1);
        addMapElement(following,t1,t2);
    }

    /**
     * Removes a preceding/following relationship between parsed symbols t1 and t2.
     * @param t1 the preceding parsed symbol.
     * @param t2 the following parsed symbol.
     */
    public void removeLink (InputSymbol t1,InputSymbol t2) 
    {
        removeMapElement(preceding,t2,t1);
        removeMapElement(following,t1,t2);
    }
    
}
